package org.fjsei.yewu.resolver.sei.inspect;

import com.querydsl.core.BooleanBuilder;
import graphql.relay.Connection;
import graphql.schema.DataFetchingEnvironment;
import md.cm.unit.Unit;
import md.cm.unit.Units;
import md.specialEqp.Equipments;
import md.specialEqp.ReportRepository;
import md.specialEqp.inspect.*;
import md.system.AuthorityRepository;
import md.system.User;
import md.system.UserRepository;
import org.fjsei.yewu.aop.MetricsLogger;
import org.fjsei.yewu.graphql.DbPageConnection;
import org.fjsei.yewu.input.IspCommonInput;
import org.fjsei.yewu.input.TaskInput;
//import org.fjsei.yewu.input.WhereTree;
//import org.fjsei.yewu.jpa.ModelFiltersImpl;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.security.JwtUserDetailsService;
import org.fjsei.yewu.util.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.*;

import java.util.List;
import java.util.UUID;


//import org.springframework.data.jpa.repository.EntityGraph;   简名同名字冲突
//@Transactional类完全限定名：而不是javax.的那一个。
//import javax.transaction.Transactional;
//import java.sql.Date;


//实际相当于controller;
//这个类名字不能重复简明！
//graphQL安全性(query/mutation返回的对象可以进行id关联嵌套查询，如何控制关联信息访问)，这方面apollo做的较好：@注释扩展。
//信息安全私密字段不建议用graphQL的关联嵌套=内省查询，独立配合用REST接口也是候选方式。
//这里接口函数比graphqls模型多出了也没关系。

@Controller
@Transactional(readOnly = true)  //?查询60秒就超时。
public class IspMgrQuery extends Comngrql  {

    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;
    @Autowired
    private Equipments eQPRepository;
    @Autowired
    private IspRepository iSPRepository;
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private TaskRepository taskRepository;
    @Autowired
    private ReportRepository reportRepository;
    @Autowired
    private Units units;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired private DetailRepository detailRepository;

    @PersistenceContext(unitName = "entityManagerFactorySei")
    private EntityManager emSei;

    //验证是否登录，谁登录，有那些角色的。只能复制或者上interface。
    //public User checkAuth() {   }报错，graphql重名!
    public Iterable<Isp> findAllISPs() {
        return iSPRepository.findAll();
    }

    public Iterable<Task> findAllTasks() {
        //task.isps的集合改动不会立刻体现，除非task自身有改动了。
        return taskRepository.findAll();
    }


    public Long countISP(UUID userId) {
        if (userId == null) return iSPRepository.count();
        User ispmen = userRepository.findById(userId).orElse(null);
        Assert.isTrue(ispmen != null,"未找到ispmen:"+ispmen);
        int myInt=iSPRepository.findByIspMen(ispmen).size();
        return Long.parseLong(new String().valueOf(myInt));
    }
    //AccessDeniedException: 不允许访问; @PreAuthorize("hasRole('ROLE_cmn'.concat(this.class.simpleName))")
    @Deprecated
    public Long countTask(String dep, String status) {
        // HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // String servletPath =request.getServletPath();
        int result;
        if (StringUtils.isEmpty(dep) && StringUtils.isEmpty(status))
            result=(int)taskRepository.count();
        else if (StringUtils.isEmpty(dep))
            result=taskRepository.findByStatus(status).size();
        else if (StringUtils.isEmpty(status))
            result=taskRepository.findByDep(dep).size();
        else
            result=taskRepository.findByDepAndStatus(dep,status).size();
        //这样方式，findByDepAndStatus 遇到参数为空的，会把null参数也直接当成了AND条件之一，必然查询结果很少了。AND :a is null
        return Long.parseLong(new String().valueOf(result));
    }
    @Deprecated
    public Isp getISP(String id) {
        Tool.ResolvedGuuid gId= Tool.fromGluuId(id);
        return iSPRepository.findById(gId.getId()).orElse(null);
    }


    //如何应对复杂的过滤和关联表查询条件呢？分页排序；其实apollo分页有3种模式offset,cursor,edges的。
    //？filter考虑升级成interface=多个参数的过滤？模式多参数聚合对象 AND OR；？先考虑简单的，offset模式。
    //Long id=最最简单的filter；      AND OR；必然在页面已经考虑和选择完毕的。
    //关联附属对象的字段来做过滤， 针对宿主对象模型， 关联能几多个？ 几层嵌套层次限制=2层最多了；。。。
    //分页针对宿主模型做的，内省的关联嵌套的，不分页但可以限制条数和深度，内省的更难控制输出记录数？内省的也排序啊。
    public Iterable<Isp> isp(Long id, int offset, int first, String orderBy) {
        List<Isp>   isplist=iSPRepository.getByDev_IdOrderById(id);
        if(offset>=isplist.size())  return null;
        if(offset<0)   offset=0;
        if(first<=0 || first>1000)  first=20;
        int toindex=offset+first;
        if(toindex>=isplist.size())     toindex=isplist.size();
        return   isplist.subList(offset, toindex);  //不含toindex的
    }

    public Iterable<Isp> findAllISPfilter(WhereTree where, int offset, int first, String orderBy, boolean asc) {
        //多个orderBy[orderBy  ? ?]
        Pageable pageable;
        if (StringUtils.isEmpty(orderBy))
            pageable = PageOffsetFirst.of(offset, first);
        else
            pageable = PageOffsetFirst.of(offset, first, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));

        //用final修饰引用类型变量p时，不能对p进行重新赋值，可以改变p里面属性的值；
        ModelFiltersImpl<Isp> modelFilters = new ModelFiltersImpl<Isp>(null);

        Specification<Isp> specification = new Specification<Isp>() {
            //这里反而放在 .effectWhereTree(where) 之后才执行的。
            @Override
            public Predicate toPredicate(Root<Isp> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                query.distinct(true);
                //Predicate predicate = cb.conjunction();
                //List<Expression<Boolean>> expressions = predicate.getExpressions();
                modelFilters.effectCount(2000);    //像回调函数一样。
                //Set<Root<?>> roots3 = query.getRoots();       //Union++ 才会有多个顶级的Root；
                //return predicate;

                return null;
            }
        };

        modelFilters.initialize(specification,emSei);
        modelFilters.effectWhereTree(where);
        List<Isp> allPage2 = iSPRepository.findAll(modelFilters);
        return allPage2;
    }
    /**任务查询：分配任务和派工的前提列表查询。
     * ?从User/me.tasks[]#node().再去关联内省更好，没必要整出独立的graphQL查询接口。
     * 部门任务分配角色人:分给底下科室或者转移其它部门，科室任务分配角色人:分给底下任务责任人或者转移其它科室，责任人个人：确保任务不会超期要派工召集人员干活或者退回Task到科室中。
     *前端揭示：我部门任务， 我科室任务， 责任人关注任务：前端管的{状态{7个enum/or常见组合}过滤[]}+时间排序?,服务对象单位过滤,时间区间[]bsType entrust, 整个Task/IspUnit单位的所有任务全列表在后台维护{任务超期了}。
     * */
    @MetricsLogger
    @QueryMapping
    public Connection<Task> findAllTaskFilter(@Argument String orderBy, @Argument Boolean asc, @Argument TaskInput where, @Argument Integer first,
                                              @Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
        //排序是必须的！！还允许选择过滤字段。
        DbPageConnection<Task> connection=new DbPageConnection(env);
        //从Relay参数来反推出offset/limit;
        int offset=connection.getOffset();
        int limit=connection.getLimit();
        //【问题】这里排序date排序ASC后的，若没有添加id做辅助排序字段，导致多次执行从数据库读的分页数据很可能是重复的，CRDB返回实际记录的顺序不一定固定。不唯一的排序组合问题。
        //前端Relay列表看出：有些loadMore()可能并没有增加新实体，重复被自动归并了,导致队列长度也没增加；浪费通信数据。
        Pageable pageable;
        if (!StringUtils.hasLength(orderBy))
            pageable = PageOffsetFirst.of(offset, limit);
        else {
            pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy, "id"));
        }
        QTask qm = QTask.task;
        BooleanBuilder builder = new BooleanBuilder();
        if(StringUtils.hasText(where.getDep()))
            builder.and(qm.dep.id.eq(Tool.fromGluuId(where.getDep()).getId()));
        if(StringUtils.hasText(where.getOffice()))
            builder.and(qm.office.id.eq(Tool.fromGluuId(where.getOffice()).getId()));
        if(null!=where.getDate1())
            builder.and(qm.date.goe(where.getDate1()));
        if(null!=where.getDate2())
            builder.and(qm.date.loe(where.getDate2()));
        if(null!=where.getStatusx() && where.getStatusx().size()>0)
            builder.and(qm.status.in(where.getStatusx()));
        if(null!=where.getEntrust())
            builder.and(qm.entrust.eq(where.getEntrust()));
        if(null!=where.getBsTypex() && where.getBsTypex().size()>0)
            builder.and(qm.bsType.in(where.getBsTypex()));
        if(StringUtils.hasText(where.getLiabler()))
            builder.and(qm.liabler.id.eq(Tool.fromGluuId(where.getLiabler()).getId()));
        if(StringUtils.hasText(where.getServu())) {
            Unit unit=fromInputUnitGlobalID(where.getServu());
            Assert.notNull(unit,"未找到Unit:"+where.getServu());
            builder.and(qm.servu.id.eq(unit.getId()));
        }
        Slice<Task> rpage= (Slice<Task>)taskRepository.findAll(builder,pageable);
        List<Task> list=(List<Task>) rpage.toList();
        //实际上SimpleListConnection也是DataFetcher<>的。内部直接提供简单cursor功能并且自己从env提取Relay参数。
        //SimpleListConnection()的输入必须是List必须是所有的node而且不能已经做了分页，
        // 给下面必须是完整Relay全部数据,SimpleListConnection自己提供游标和分页能力。
        return connection.setListData(list).get(env);
    }

    /**最老版：使用前端自主编写Task.Where条件的时代，代码逻辑是前端录入的，生成SQL语句的自由度太大，目前淘汰*/
    public Iterable<Task> findAllTaskFilter删除(WhereTree where, int offset, int first, String orderBy, boolean asc) {
        if(first<=0)   first=20;
        Pageable pageable;
        if (StringUtils.isEmpty(orderBy))
            pageable = PageOffsetFirst.of(offset, first);
        else
            pageable = PageOffsetFirst.of(offset, first, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));
        ModelFiltersImpl<Task> modelFilters = new ModelFiltersImpl<Task>(null);
        Specification<Task> specification = new Specification<Task>() {
            @Override
            public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                query.distinct(true);
                modelFilters.effectCount(2000);    //像回调函数一样。
                return null;
            }
        };
        modelFilters.initialize(specification,emSei);
        modelFilters.effectWhereTree(where);
        Page<Task> items= taskRepository.findAll(modelFilters,pageable);
        return items;
    }
    /*
    //最多1条正常状态的ISP， 唯一性保证： device/157/task/184 ;未派工的null task isp
    public Isp getISPofDevTask(Long dev, Long task) {
        List<Isp> allPage =iSPRepository.getByDev_IdAndTask_IdOrderByNextIspDate(dev,task);
        if(allPage.size()==0)     return null;
        else return allPage.get(0);
    }
    */

    /**查当前登录用户user正在关联关心的Isp
     * 直接给出参数字段去查询的独立接口Query模式。
     * QueryDsl：多层关联的内省有时会空指针问题;
     * 根据Isp.report主报告的stm流转状态机做过滤； #问题：iSPRepository历史报告中查找的？查询性能有问题？数据一致性+延时容忍性+搜索速度?IspEs模型？消息队列todoList?
     * 【zeebe引入】之后，这些需要全部改掉，不是依赖关系数据库的状态机流转表的搜索和状态过滤，今后改造：需要从流程引擎服务获取用户任务列表了。
     * */
    @Deprecated
    @QueryMapping
    public Connection<Isp> findMeIsps(@Argument String orderBy, @Argument Boolean asc, @Argument IspCommonInput where, @Argument Integer first, @Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
        User user= checkAuth();
        if(user==null)   return null;
        DbPageConnection<Isp> connection=new DbPageConnection(env);
        int offset=connection.getOffset();
        int limit=connection.getLimit();
        Pageable pageable;
        if (!StringUtils.hasLength(orderBy))
            pageable = PageOffsetFirst.of(offset, limit);
        else
            pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));
        QIsp qm = QIsp.isp;
        BooleanBuilder builder = new BooleanBuilder();
        BooleanBuilder userbuild = new BooleanBuilder();
        userbuild.or(qm.report.stm.master.eq(user)).or(qm.report.stm.authr.contains(user));
        BooleanBuilder stabuild = new BooleanBuilder();
        stabuild.or(qm.report.stm.sta.eq(Procedure_Enum.BEGIN)).or(qm.report.stm.sta.eq(Procedure_Enum.MAKE));
        BooleanBuilder detailBuild = new BooleanBuilder();
        detailBuild.or(qm.bus.isNotNull());
        builder.and(detailBuild).and(userbuild).and(stabuild);
/*        List<Isp>  isps = new ArrayList<Isp>();
        Iterable<Isp> rpage= iSPRepository.findAllNc(builder,pageable);
        rpage.forEach(item -> {
            isps.add(item);
        });*/
        Slice<Isp> rpage= (Slice<Isp>)iSPRepository.findAll(builder,pageable);
        List<Isp> isps=(List<Isp>) rpage.toList();
        return connection.setListData(isps).get(env);
    }






    //2020-8-18升级后不能用WhereTree来做前端查询了！input类型不直接使用Object Type呢？Object字段可能存在循环引用，或字段引用不能作为查询输入的接口和联合类型。
    //BUG导致graphQL input递归无法再使用！ModelFilters这层类sql接口预备给高权限场景使用WhereTree，普通接口走参数定做模式/多写代码。
    public Iterable<Eqp> findAllEQPsFilter_delete(WhereTree where, int offset, int first, String orderBy, boolean asc) {
        User user= checkAuth();
        if(user==null)   return null;
        //这里可以增加后端对　查询的权限控制，控制关注许可后的　某用户可以查询那些
        if(first<=0)   first=20;
        Pageable pageable;
        if (StringUtils.isEmpty(orderBy))
            pageable = PageOffsetFirst.of(offset, first);
        else
            pageable = PageOffsetFirst.of(offset, first, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));

        ModelFiltersImpl<Eqp> modelFilters = new ModelFiltersImpl<Eqp>(null);
        Specification<Eqp> specification = new Specification<Eqp>() {
            @Override
            public Predicate toPredicate(Root<Eqp> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                query.distinct(true);
                modelFilters.effectCount(2000);    //像回调函数一样。
                return null;
            }
        };

        modelFilters.initialize(specification,emSei);
        modelFilters.effectWhereTree(where);
        /* 这下面对照是这样的： query级缓存时间内，数据库人工修改的，前端靠findAllEQPsFilter查的会滞后才显示。
            注意query-results缓存时间内select from结果集不变，但是列表中某单一个实体的字段却可变动的；人工删除实体会报错。
            @QueryHints(value = { @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_CACHEABLE, value = "true") } )
            Page<Eqp> findAll(@Nullable Specification<Eqp> spec, Pageable pageable);
        */
        Page<Eqp> list = eQPRepository.findAll(modelFilters,pageable);
        return list;
    }
    public Iterable<Eqp> findAllEQPsFilter_删除(DeviceCommonInput where, int offset, int first, String orderBy, boolean asc) {
        User user= checkAuth();
        if(user==null)   return null;
        //这里可以增加后端对　查询的权限控制，控制关注许可后的　某用户可以查询那些
        if(first<=0)   first=20;
        Pageable pageable;
        if (StringUtils.isEmpty(orderBy))
            pageable = PageOffsetFirst.of(offset, first);
        else
            pageable = PageOffsetFirst.of(offset, first, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));

        ModelFiltersImpl<Eqp> modelFilters = new ModelFiltersImpl<Eqp>(null);
        Specification<Eqp> specification = new Specification<Eqp>() {
            @Override
            public Predicate toPredicate(Root<Eqp> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                query.distinct(true);
                modelFilters.effectCount(2000);    //像回调函数一样。
                return null;
            }
        };

        modelFilters.initialize(specification,emSei);
        //modelFilters.effectWhereTree(where);

        /* 这下面对照是这样的： query级缓存时间内，数据库人工修改的，前端靠findAllEQPsFilter查的会滞后才显示。
            注意query-results缓存时间内select from结果集不变，但是列表中某单一个实体的字段却可变动的；人工删除实体会报错。
            @QueryHints(value = { @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_CACHEABLE, value = "true") } )
            Page<Eqp> findAll(@Nullable Specification<Eqp> spec, Pageable pageable);
        */
        Page<Eqp> list = eQPRepository.findAll(modelFilters,pageable);
        return list;
    }
    /**
     * 用这样WhereTree, ModelFilters来过滤排序的模式，在后端操作比较繁琐。
     * 前端用的话比较方便，但是安全性较低，只能适用大权限前端调用模式。
     */
    @Deprecated
    public Iterable<User> findAllUserFilter_Way(WhereTree where, int offset, int first, String orderBy, boolean asc) {
        User user = checkAuth();
        if (user == null) return null;
        //这里可以增加后端对　查询的权限控制，控制关注许可后的　某用户可以查询那些
        if (first <= 0) first = 20;
        Pageable pageable;
        if (StringUtils.isEmpty(orderBy))
            pageable = PageOffsetFirst.of(offset, first);
        else
            pageable = PageOffsetFirst.of(offset, first, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));

        ModelFiltersImpl<User> modelFilters = new ModelFiltersImpl<User>(null);
        Specification<User> specification = new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                query.distinct(true);
                modelFilters.effectCount(2000);    //像回调函数一样。
                return null;
            }
        };

        modelFilters.initialize(specification, emSei);
        modelFilters.effectWhereTree(where);
        Page<User> recipes = userRepository.findAll(modelFilters, pageable);
        return recipes;
    }
    public long countAllEQPsWhere(WhereTree where) {
        Specification<Eqp> spec = new Specification<Eqp>() {
            @Override
            public Predicate toPredicate(Root<Eqp> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicateList = new ArrayList<Predicate>();
           /*     if (!StringUtils.isEmpty(filter.getCod())) {
                    Path<String> p = root.get("type");
                    predicateList.add(cb.like(p,"%" + filter.getCod() + "%"));
                }
                if (!StringUtils.isEmpty(filter.getOid())) {
                    Path<String> p = root.get("fNo");
                    predicateList.add(cb.like(p,"%" + filter.getOid() + "%"));
                }
                Predicate[] predicates = new Predicate[predicateList.size()];
                predicateList.toArray(predicates);
                query.where(predicates);
                */
                return null;
            }
        };
        return eQPRepository.count(spec);
    }
}



/*执行策略ExecutionStrategy都会导致lazy失败，这里读取已跨越DB的session延迟异步读取啊，
搞成了Entity和GraphQLResolver<TYPE>两个类独立分离了，还一样Lazy懒加载错误。
像@OneToMany这类JPA注解关联关系：对方类型必须是实体的。Object对象类型不能是接口interface的。
@MappedSuperclass注解的使用 Entity类之间的继承关系    https://blog.csdn.net/zty1317313805/article/details/80524900
使用懒加载时，报异常：session失效https://blog.csdn.net/wanping321/article/details/79532918
EntityManager不会立刻关闭，导致连接池连接数占用。高并发的系统最好不要使用OpenEntityManagerInView模式；https://blog.csdn.net/q1054261752/article/details/54773428
Spring动态替换Bean 接口=BeanPostProcessor； https://www.jianshu.com/p/853a081e4a02
若toPredicate内部应用：Subquery无法一次性选择多个字段出来做表达式比较条件!!，附加新建一个CriteriaQuery却又无法连接原始query报错无法serialize;
JPA关联嵌套子查询correlate subquery；而From()是不支持子查询的。 非关联子查询指子查询可以脱离主查询独立执行；关联子查询限制是子查询不能返回多于1行的数据.
*/


